package com.svconnect.plugin.builders;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.svconnect.plugin.model.FileContent;
import com.svconnect.plugin.model.wsdl.ComplexDataType;
import com.svconnect.plugin.model.wsdl.EComplexType;
import com.svconnect.plugin.model.wsdl.IDataType;
import com.svconnect.plugin.model.wsdl.Operation;
import com.svconnect.plugin.model.wsdl.Parameter;
import com.svconnect.plugin.model.wsdl.Service;
import com.svconnect.plugin.model.wsdl.SimpleDataType;
import com.svconnect.plugin.util.Log;
import com.svconnect.plugin.util.StringUtil;

public class ServiceClientBuilder extends BuilderBase {
	
	public static final String INTERFACE_PREFIX = "I";
	private String packageName;

	private String dataContractPackageName;

	private String clientClassNameSuffix = "Client";
	private boolean exceptionsEnabled = true;

	public ServiceClientBuilder(String packageName, String referenceDir) {
		this.packageName = packageName;
		this.dataContractPackageName = packageName;
		this.setReferenceDir(referenceDir);
	}
	
	public ServiceClientBuilder(String packageName, String dataModelPackageName, String referenceDir) {
		this.packageName = packageName;
		this.dataContractPackageName = dataModelPackageName;
		this.setReferenceDir(referenceDir);
	}
	
	public String getDataContractPackageName() {
		return dataContractPackageName;
	}

	public void setDataContractPackageName(String dataContractPackageName) {
		this.dataContractPackageName = dataContractPackageName;
	}

	public void setClientClassNameSuffix(String clientClassNameSuffix) {
		this.clientClassNameSuffix = clientClassNameSuffix;
	}
	
	public boolean isExceptionsEnabled() {
		return exceptionsEnabled;
	}

	public void setExceptionsEnabled(boolean exceptionsEnabled) {
		this.exceptionsEnabled = exceptionsEnabled;
	}	
	
	public String getPackageName() {
		return packageName;
	}

	public void setPackageName(String packageName) {
		this.packageName = packageName;
	}	
	
	public boolean build(List<Service> services) {
		Log.i("Building service client classes");
		List<FileContent> clss = this.buildClasses(services);
		if (clss!=null) {
			try {
				this.createPackageDir(services, packageName);
				this.save(clss);
				return true;
			} catch (IOException e) {
				return false;
			}
		}
		return false;
	}
	
	private void save(List<FileContent> classes) throws IOException {
		if (classes!=null) {
			for(FileContent cls : classes) {
				String filename = cls.getClassName()+".java";
				Log.i("Creating "+filename);
				String filepath = this.getPackageDir(cls.getService().getName(),packageName)+"/"+filename;
				FileWriter fw = new FileWriter(filepath);
				fw.write(cls.getClassContent());
			}
		}
	}	
	
	private List<FileContent> buildClasses(List<Service> services) {
		if (services!=null) {
			List<FileContent> classes = new ArrayList<FileContent>();
			for(Service svc : services) {
				FileContent cls = this.buildServiceInterface(svc);
				cls.setService(svc);
				classes.add(cls);
				cls = this.buildServiceClient(svc);
				cls.setService(svc);
				classes.add(cls);
			}
			return classes;
		}
		return null;
	}
	
	private boolean isGenericReturned(Service svc) {
		if (svc!=null && svc.getOperations()!=null) {
			for(Operation o : svc.getOperations()) {
				if (o.getResponse() instanceof ComplexDataType) {
					ComplexDataType cdt =(ComplexDataType)o.getResponse();
					if (cdt.getType()==EComplexType.DICTIONARY || cdt.getType()==EComplexType.SEQUENCE) {
						return true;
					}
				}
			}
		}
		return false;
	}
	
	
	private FileContent buildServiceClient(Service svc) {
		FileContent cls = new FileContent();
		cls.setClassName(svc.getName()+this.clientClassNameSuffix);
		
		StringBuilder content = new StringBuilder();
		content.append(String.format("package %s;\n\n", this.packageName));
		content.append(String.format("import %s.services.*;\n", BuilderBase.LIBRARY_PACKAGE));
		
		if (!this.packageName.equals(this.dataContractPackageName))
			content.append(String.format("import %s.*;\n", this.dataContractPackageName));
		
		String interfaceName = INTERFACE_PREFIX+svc.getName();

		content.append("\n");
		if (this.isGenericReturned(svc))
			content.append("@SuppressWarnings(\"unchecked\")\n");
		content.append(String.format("public class %s extends ServiceBase<%s> implements %s  { \n\n", 
				cls.getClassName(), interfaceName, interfaceName));

		content.append(String.format("\tpublic %s() {}\n\n", cls.getClassName()));
		content.append(String.format("\tpublic %s(String endpointName) {\n", cls.getClassName()));
		content.append("\t\tsuper(endpointName);\n\t}\n\n");
		content.append(String.format("\tpublic %s(String endpointName, String endpointAddress) {\n", cls.getClassName()));
		content.append("\t\tsuper(endpointName,endpointAddress);\n\t}\n\n");		
		
		List<Operation> operations = svc.getOperations();
		for(Operation op : operations) {
			IDataType responseType = op.getResponse();
			List<Parameter> params = op.getParameters();
			
			String opName = op.getName();
			if (this.isJavaConventionEnabled())
				opName = StringUtil.toLowerCaseFirst(opName);

			content.append(String.format("\tpublic %s %s(", responseType.toString(), opName));
			int paramCount = params.size();
			for(int i=0; i<paramCount; i++) {
				Parameter p = params.get(i);
				String pName = p.getName();
				if (this.isJavaConventionEnabled())
					pName = StringUtil.toLowerCaseFirst(pName);
				
				content.append(p.getType().toString());
				content.append(" ");
				content.append(pName);
				
				if (i != paramCount-1)
					content.append(", ");
			}
			content.append(")");
			
			if (isExceptionsEnabled()) {
				List<ComplexDataType> faults = op.getFaults();
				int faultCount = faults.size();
				if (faultCount>0)
					content.append(" throws ");
				for(int i=0;i<faultCount;i++) {
					ComplexDataType f = faults.get(i);
					content.append(f.getName());
					if (i!=faultCount-1)
						content.append(", ");
				}
			}
			
			content.append(" {\n");
			content.append("\t\tInvocationParamList params = new InvocationParamList();\n");
			
			for(int i=0; i<paramCount; i++) {
				Parameter p = params.get(i);
				String pName = p.getName();
				if (this.isJavaConventionEnabled())
					pName = StringUtil.toLowerCaseFirst(pName);
				
				boolean isDictionary = false;
				if (p.getType() instanceof ComplexDataType) {
					ComplexDataType cdt = (ComplexDataType)p.getType();
					if (cdt.getType()==EComplexType.DICTIONARY) {
						isDictionary = true;
						content.append(String.format("\t\tparams.add(new DictInvocationParam(\"%s\",%s,\"%s\"));\n",pName,pName,cdt.getMembers().get(0).getType()));
					}
				}
				
				if (!isDictionary)
					content.append(String.format("\t\tparams.add(new InvocationParam(\"%s\",%s));\n",pName,pName));
			}
			
			StringBuilder invocationStr = new StringBuilder();
			invocationStr.append("\t\t");
			if (op.getResponse()!=SimpleDataType.VOID) {
				invocationStr.append("return (");
				invocationStr.append(op.getResponse().toString());
				invocationStr.append(")");
			}
			invocationStr.append("this.invoke(params);\n");			
			if (isExceptionsEnabled()) {
				invocationStr = this.surroundWithTryCatch(invocationStr, op);
			}
			
			content.append(invocationStr.toString());
			content.append("\t}\n\n");
		}
		content.append("}\n");
		cls.setClassContent(content.toString());
		
		return cls;
	}
	
	private StringBuilder surroundWithTryCatch(StringBuilder invocationStr, Operation op)  {
		StringBuilder result = invocationStr;
		List<ComplexDataType> faults = op.getFaults();
		if (faults!=null && faults.size()>0) {
			int i=0;
			result = new StringBuilder();
			result.append("\t\ttry {\n");
			result.append("\t");
			result.append(invocationStr.toString());
			result.append(String.format("\t\t} catch(%s.exceptions.FaultException e) {\n", BuilderBase.LIBRARY_PACKAGE));
			result.append("\t\t\tif(e.getDetail()!=null) {\n");
			for(ComplexDataType fault : faults) {
				result.append("\t\t\t\t");
				if (i==0) result.append("if");
				else result.append("else if");
				result.append(" (e.getDetail() instanceof ");
				result.append(fault.getName());
				result.append(")\n");
				result.append("\t\t\t\t\t");
				result.append("throw (");
				result.append(fault.getName());
				result.append(")e.getDetail();\n");
				i++;
			}
			result.append("\t\t\t}\n");
			result.append("\t\t\tthrow e;\n");
			result.append("\t\t}\n");
		}
		return result;
	}
	
	private FileContent buildServiceInterface(Service svc) {
		FileContent cls = new FileContent();
		cls.setClassName(INTERFACE_PREFIX+svc.getName());
		
		StringBuilder content = new StringBuilder();
		content.append(String.format("package %s;\n\n", this.packageName));
		content.append(String.format("import %s.annotations.OperationContract;\n", BuilderBase.LIBRARY_PACKAGE));
		content.append(String.format("import %s.annotations.ServiceContract;\n\n", BuilderBase.LIBRARY_PACKAGE));
		if (!this.packageName.equals(this.dataContractPackageName))
			content.append(String.format("import %s.*;\n\n", this.dataContractPackageName));
		
		content.append(String.format("@ServiceContract(namespace=\"%s\")\n", svc.getNamespace()));
		content.append(String.format("public interface %s  { \n\n", cls.getClassName()));

		List<Operation> operations = svc.getOperations();
		for(Operation op : operations) {
			IDataType responseType = op.getResponse();
			List<Parameter> params = op.getParameters();
			
			String opName = op.getName();
			if (this.isJavaConventionEnabled())
				opName = StringUtil.toLowerCaseFirst(opName);

			content.append(String.format("\t@OperationContract(name=\"%s\", action=\"%s\")\n",op.getName(), op.getAction()));
			content.append(String.format("\tpublic %s %s(", responseType.toString(), opName));
			int paramCount = params.size();
			for(int i=0; i<paramCount; i++) {
				Parameter p = params.get(i);
				String pName = p.getName();
				if (this.isJavaConventionEnabled())
					pName = StringUtil.toLowerCaseFirst(pName);
				
				content.append(p.getType().toString());
				content.append(" ");
				content.append(pName);
				
				if (i != paramCount-1)
					content.append(", ");
			}
			content.append(")");
			List<ComplexDataType> faults = op.getFaults();
			int faultCount = faults.size();
			if (faultCount>0)
				content.append(" throws ");
			for(int i=0;i<faultCount;i++) {
				ComplexDataType f = faults.get(i);
				content.append(f.getName());
				if (i!=faultCount-1)
					content.append(", ");
			}
			
			content.append(";\n\n");
		}
		content.append("}\n");
		cls.setClassContent(content.toString());
		
		return cls;		
	}
	
}
